<script>
  import { SettingsContext } from '$lib/settings/context.svelte';
  import { ConnectedList, ConnectedListItem } from '$lib/components/connected-list';

  const settings = SettingsContext.get();
</script>

## Acknowledgement

This package relies on [Svelte action] and attempts to stay minimal. If you are looking for a declarative, component-oriented solution, check out [janosh/svelte-toc].

## Installation

<enhanced-code-block group display="tabs" bind:title={settings.packageManager}>

```bash title=npm
npm install --save-dev @svelte-put/toc
```

```bash title=pnpm
pnpm add -D @svelte-put/toc
```

```bash title=yarn
yarn add -D @svelte-put/toc
```

</enhanced-code-block>

<div class="c-callout c-callout--success c-callout--megaphone">

  New to Svelte 5? See [Migration Guides](#migration-guides).

</div>

## Introduction

`@svelte-put/toc` operates at **runtime** and does the following:

<ConnectedList class="pl-4">
  <ConnectedListItem>

search for matching elements (default: `:where(h1, h2, h3, h4, h5, h6)`),

  </ConnectedListItem>
  <ConnectedListItem>

generate `id` attribute from element `textContent`,

  </ConnectedListItem>
  <ConnectedListItem>

add anchor tag to element,

  </ConnectedListItem>
  <ConnectedListItem>

attach [IntersectionObserver] to each matching element to track its visibility
on the screen,

  </ConnectedListItem>
  <ConnectedListItem>

expose necessary pieces for building table of contents.

  </ConnectedListItem>
</ConnectedList>

It is recommended to use the complementary [@svelte-put/preprocess-auto-slug] package for handling <span class="c-num">2</span> and <span class="c-num">3</span> at **compile time**. `toc` will skip those operations if they are already handled by `preprocess-auto-slug`.

<div class="c-callout c-callout--info">

The table of contents in this documentation site is generated by `toc` itself. Check out [its source code here](https://github.com/vnphanquang/svelte-put/blob/890f2104be848148d7a3a0d3c6869a0f885eda9e/sites/docs/src/routes/(site)/%2Blayout.svelte#L25-L33) (search for `toc`).

</div>

<h2 id="quick-start">Quick Start</h2>

Given the following Svelte source code, let's see how `toc` does its job.

```svelte title="+page.svelte"
<script>
  import { Toc } from '@svelte-put/toc';
  // :::highlight
  const toc = new Toc({ observe: true });
  // :::
</script>

<!-- :::highlight -->
<main use:toc.actions.root>
<!-- ::: -->
  <h1>Page Heading</h1>

  <!-- :::highlight -->
  <section>
    <h2>Table of Contents</h2>
    {#if toc.items.size}
      <ul>
        {#each toc.items.values() as tocItem (tocItem.id)}
          <li>
            <!-- svelte-ignore a11y_missing_attribute -->
            <a use:toc.actions.link={tocItem}>
              <!-- textContent injected by toc -->
            </a>
          </li>
        {/each}
      </ul>
    {/if}
  </section>
  <!-- ::: -->

  <section>
    <h2>Section Heading Level 2</h2>
    <p>...</p>
  </section>

  <section>
    <h3>Section Heading Level 3</h3>
    <p>...</p>
  </section>
  <!-- ... -->
</main>
```

Notice the highlighted lines, specifically:

- `new Toc({ ... })` creates a `Toc` instance, powered by Svelte runes, whose `items` property will be populated with the extracted toc elements and can track `activeItem` if the `observe` option is set to `true`,
- The associated `toc.actions.root` action is placed on the parent whose descendants will be traversed to collect toc elements. See [toc.actions.root] for more details.
- The associated `toc.actions.link` action is placed on anchor tags within the table of contents to quickly setup clickable link to matching toc elements. See [toc.actions.link] for more details.

The output will look something like this:

```html title=output.html
<main
  data-toc-observe-for="page-heading"
  data-toc-root="ee4f13a3-dfec-401d-b52c-a52550e20ddf"
  data-toc-observe-active-id="section-heading-level-3"
>
  <h1 id="page-heading" data-toc="">
    <a aria-hidden="true" tabindex="-1" href="#page-heading" data-toc-anchor="">#</a>Page Heading
  </h1>
  <section data-toc-observe-for="table-of-contents">
    <h2 id="table-of-contents" data-toc="">
      <a aria-hidden="true" tabindex="-1" href="#table-of-contents" data-toc-anchor="">#</a>Table of
      Contents
    </h2>
    <ul>
      <li>
        <a href="#page-heading" data-toc-link-for="page-heading" data-toc-link-current="false"
          >Page Heading</a
        >
      </li>
      <li>
        <a
          href="#table-of-contents"
          data-toc-link-for="table-of-contents"
          data-toc-link-current="false">Table of Contents</a
        >
      </li>
      <li>
        <a
          href="#section-heading-level-2"
          data-toc-link-for="section-heading-level-2"
          data-toc-link-current="false">Section Heading Level 2</a
        >
      </li>
      <li>
        <a
          href="#section-heading-level-3"
          data-toc-link-for="section-heading-level-3"
          data-toc-link-current="true">Section Heading Level 3</a
        >
      </li>
    </ul>
  </section>
  <section data-toc-observe-for="section-heading-level-2">
    <h2 id="section-heading-level-2" data-toc="">
      <a aria-hidden="true" tabindex="-1" href="#section-heading-level-2" data-toc-anchor="">#</a
      >Section Heading Level 2
    </h2>
    <p>...</p>
  </section>
  <section data-toc-observe-for="section-heading-level-3">
    <h3 id="section-heading-level-3" data-toc="">
      <a aria-hidden="true" tabindex="-1" href="#section-heading-level-3" data-toc-anchor="">#</a
      >Section Heading Level 3
    </h3>
    <p>...</p>
  </section>
</main>
```

<h2 id="toc-class">Toc Class</h2>

Instantiate the `Toc` class is the first required step. It accepts a `TocInit` object with the following interface:

<div class="c-callout c-callout--info c-callout--icon-bulb">

The code snippet below only show the top level config properties. Note that everything is optional, toc can be used without any parameter at all. Visit [TocObserve][toc.observe] and [TocAnchor][toc.anchor] sections for their respective config interfaces.

</div>

```typescript title="TocInit"
export interface TocInit {
	/**
	 * the query selector used to find all matching
	 * DOM elements.
	 * Default to: `:where(h1, h2, h3, h4, h5, h6)`
	 */
	selector?: string;
	/**
	 * query selector(s) that match DOM elements to ignore
	 * Each selector is used as `:not(selector)`.
	 * Default to: `.toc-exclude`
	 *
	 * Alternatively, you can set the `data-toc-ignore` attribute on the element
	 * Default to: `[]`
	 */
	ignore?: string[] | string;
	/**
	 * inline `scroll-margin-top` value applied matching elements.
	 * Default to: `0`
	 */
	scrollMarginTop?: number | string | ((element: HTMLElement) => number | string);
	/**
	 * instructions to add the anchor tag.
	 * Default to: `true`
	 */
	anchor?: boolean | TocAnchorConfig;
	/**
	 * instructions to track the active element in the viewport using `IntersectionObserver`.
	 * Default to: `false`
	 */
	observe?: boolean | TocObserveConfig;
}
```

## Actions

<h3 id="toc-actions-root">Toc Root</h3>

`use:toc.actions.root` is a required step that will search for matching elements from descendants of the element the action is attached to. In [Quick Start], that's the `<main>` element.

```svelte
<main use:toc.actions.root>
```

To search from everything on the page, use it on `<svelte:body>`.

```svelte
<svelte:body use:toc>
```

<div class="c-callout c-callout--warning">

**No Dynamic Update**

During development, you may notice that `toc` does not update when you change the action parameters at runtime and will require a page refresh to work again. This is because currently `toc.actions.root` only runs once on mount.

Supporting dynamic update is quite a task (tracking what's changed and avoiding duplicate operations) that will increase the bundle size & complexity but is not practically useful in most use cases (how often does a table of contents change at runtime?).

If you think otherwise and have a valid use case, please [raise an issue][issue].

</div>

<h3 id="toc-actions-link">Toc Link</h3>

`user:toc.actions.link` is an **optional** complementary action used on an `<a>`. It requires a mandatory parameter - a `TocItem` object (value of `toc.items`), as seen in [Quick Start]:

```svelte title="+page.svelte"
<section>
  <h2>Table of Contents</h2>
  {#if toc.items.size}
    <ul>
      {#each toc.items.values() as tocItem}
        <li>
          <!-- :::highlight -->
            <!-- svelte-ignore a11y_missing_attribute -->
            <a use:toc.actions.link={tocItem}>
              <!-- textContent injected by toc -->
            </a>
          <!-- ::: -->
        </li>
      {/each}
    </ul>
  {/if}
</section>
```

By default, it does the following:

1. inject the `textContent` of the element associated with the `TocItem` object into the anchor tag,
2. set the `href` attribute to the `id` of the element associated with the `TocItem` object,
3. toggle on the `data-toc-link-active` attribute [when the element is in view][toc.observe], given the `observe` option is enabled upon [Toc instance][toc.class] creation.

Regarding markup, it is essentially the same as:

```svelte title=not-using-link-action.svelte
<section>
  <h2>Table of Contents</h2>
  {#if toc.items.size}
    <ul>
      {#each toc.items.values() as { id, text } (id)}
        <li>
          <!-- :::highlight -->
          <a href="#{id}" data-toc-link-active={toc.activeItem?.id === id}>{text}</a>
          <!-- ::: -->
        </li>
      {/each}
    </ul>
  {/if}
</section>
```

However, `toclink` does provide additional click event listener that makes sure the toc item being scrolled to will be the active one, which is not guaranteed otherwise. This is because the package relies on [IntersectionObserver], and when a matching toc element is scrolled into view, the next one might already intersects enough with viewport to become the active one.

<div class="c-callout c-callout--info">

In short, unless you need full control over the behavior of the anchor tag, it is recommended to use `toc.actions.link` for consistency and conciseness. Further customization to `toc.actions.link` can be passed to the `observe.link` config property of the [Toc instance][toc.class]. See [Observing "In View" Element][toc.observe] for more details.

</div>

<h2 id="observing-in-view-element">Observing "In View" Element</h2>

A common feature of a table of contents on the web is to track which section is "in view". Traditionally this has been done by subscribing to the `scroll` event. With the relatively new [IntersectionObserver] on the scene, however, we can do this in a more performant manner.

By default, this feature is disabled. To turn it on, set the `observe` option to `true` during [Toc instance][toc.class] creation...

```typescript title="observe: true"
import { Toc } from '@svelte-put/toc';
const toc = new Toc({ observe: true });
```

...or provide an object for verbose customization with the following interface:

```typescript title="TocObserveConfig"
/**
 * options to config how `toc` action create `IntersectionObserver` for each
 * matching toc element
 */
export interface TocObserveConfig extends Omit<IntersectionObserverInit, 'threshold'> {
	/**
	 * whether to add `IntersectionObserver` to each matching toc element
	 * to track active active element in the viewport.
	 * Default to: `true`
	 */
	enabled?: boolean;
	/**
	 * strategy to observe matching toc elements.
	 *
	 * - `'parent'` — observe the parent element of the matching toc element
	 *
	 * - `'self'` — observe the matching toc element itself
	 *
	 * - `'auto'` — attempt to compare matching toc element & its parent `offsetHeight` with
	 * `window.innerHeight` to determine the best strategy.
	 *
	 * Default to: `auto`
	 *
	 * Alternatively, this can be overridden per element by setting the `data-toc-strategy` attribute
	 * on that element.
	 */
	strategy?: 'parent' | 'self' | 'auto';
	/**
	 * threshold passed to `IntersectionObserver`.
	 * Default to: `(element) => Math.min((0.8 * window.innerHeight) / element.offsetHeight, 1)`
	 *
	 * Alternatively, `data-toc-threshold` (number) attribute can be set on
	 * the matching toc element
	 */
	threshold?: number | ((element: HTMLElement) => number);
	/**
	 * behavioral configuration for elements that `use:toc.actions.link={tocItem}` is placed on.
	 */
	link?: {
		/**
		 * whether to enable this configuration
		 * Default to: `false`
		 */
		enabled?: boolean;
		/**
		 * throttle the observe of `use:toc.actions.link` on click
		 *
		 * This ensures that the active toc item will be
		 * the same one that this link is pointing to.
		 * Otherwise, it is not guaranteed so, because `observe`
		 * is handled with `IntersectionObserver` the next items might
		 * already comes into viewport when this link is clicked.
		 *
		 * Set to 0 to disable throttling.
		 *
		 * Default to: `800`
		 */
		throttleOnClick?: number;
		/**
		 * boolean attribute(s) to indicate if this
		 * is linking to the active toc item
		 *
		 * For this to work, it is required that `tocItem` be provided
		 * or the href is in the form `'#<toc-item-id>'`
		 *
		 * By default, `toclink` uses {@link https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver | MutationObserver}
		 *
		 * Set `false` to disable this behavior
		 *
		 * Default to: `'data-toc-link-active'`
		 */
		activeAttribute?: string | string[] | boolean;
	};
}
```

### Caveat

Although this may not have much impact on casual users, [IntersectionObserver] unfortunately comes with its own caveat. For `onscroll`, we can achieve something like:

> For an element (typically heading), when it reaches 10% offset from the top of viewport, set it as active.

This is not trivial with `IntersectionObserver` without some hacking (to my knowledge at least), because `IntersectionObserver` triggers callback when element (or part of it) intersects with the viewport. For this reason, `toc` prefers to "think" in terms of "section" rather than individual element, something like this:

> When 80% of a section is visible within the viewport (threshold of `0.8` for `IntersectionObserver`), set it to active.

With this design decision, the most "natural" pattern is to wrap heading tag and its associated content within a `<section>` (as shown in [Quick Start]).

```svelte title="Think sections..."
<!-- :::diff + -->
<section>
<!-- ::: -->
  <h2>Heading, whether it is h2,h3,...</h2>
  <p>...content...</p>
<!-- :::diff + -->
</section>
<!-- ::: -->
```

<div class="c-callout c-callout--success">

Grouping content into sections as discussed above will help `toc` track the active section more accurately, but it is **NOT** mandatory; things will work just fine with flat headings and content; it will just be a *tiny* bit less accurate. This is especially helpful for setup that doesn't allow easily wrapping content in sections, such as markdown-based content.

</div>

### Observe Strategies

This section discusses in details how to configure the strategy to observe toc elements. Feel free to skip to [the next section](#toc-anchor) if it is not relevant to you.

There are three observe strategies use by `toc`, set by the global `observe.strategy` property on [Toc instance][toc.class] config or per-element via the `data-toc-strategy` attribute:

```typescript
'self' | 'parent' | 'auto'
```

By default, `observe.strategy` is set to `auto` (recommended), which relies on the following algorithm:

1. `data-toc-strategy` takes highest precedence and is used if set on the toc element, otherwise...
2. if `observe.strategy` is set, use it, ...
3. if strategy is now `auto`:
   - if the element's parent height is less than 80% of the viewport height, forward to the `parent` strategy, else
   - forward the `self` strategy,
4. now the strategy is narrowed down to either `parent` or `self`, i.e. `self` uses the matching element itself as the target, while `parent` uses the parent element.

Similarly, the threshold for [IntersectionObserver] can be set via the global `observe.threshold` property on [Toc instance][toc.class] config or per-element via the `data-toc-threshold` attribute.

<h2 id="toc-anchor">Wrapping Toc Element in Anchor Tag</h2>

If not handled by [@svelte-put/preprocess-auto-slug], `toc` will attempt to add an anchor tag to each matching element, similar to how Github adds anchor tags to headings in a rendered `README`.

```svelte title="Inserting anchor tag"
<!-- svelte input -->
<h2>Section Heading Level 2</h2>

<!-- html output -->
<h2 id="section-heading-level-2" data-toc="">
  <!-- :::highlight -->
  <a aria-hidden="true" tabindex="-1" href="#section-heading-level-2" data-toc-anchor="">#</a>
  <!-- ::: -->
  Section Heading Level 2
</h2>
```

Configuration to how anchor tags are inserted (or not) can be specified via `anchor` option in the [Toc instance][toc.class] config, which takes either a `boolean` (defaults to `true`, set to `false` to not insert tag)...

```typescript title="anchor: false"
import { Toc } from '@svelte-put/toc';
const toc = new Toc({ anchor: false });
```

...or a config object with the following interface:

```typescript title="TocAnchorConfig"
/**
 * options to config how `toc` action inject anchor tag for each matching toc element
 */
export interface TocAnchorConfig {
	/** whether to insert an anchor tag for each matching node */
	enabled?: boolean;
	/**
	 * where to create the anchor tag
	 *
	 * - 'prepend' — inject link before the target tag text
	 *
	 * - 'append' — inject link after the target tag text
	 *
	 * - 'wrap' — wrap the whole target tag text with the link
	 *
	 * - 'before' — insert link before the target tag
	 *
	 * - 'after' — insert link after the target tag
	 * Default to: 'prepend'
	 */
	position?: 'prepend' | 'append' | 'wrap' | 'before' | 'after';
	/**
	 * content of the inserted anchor tag,
	 * ignored when behavior is `wrap`.
	 * Default to: '#
	 */
	content?: string;
	/**
	 * href attribute of the inserted anchor tag
	 * Default to: `href: (id) => '#' + id`
	 */
	href?: (id: string) => string;
	/**
	 * properties set to the inserted anchor tag,
	 * Default to: `{ 'aria-hidden': 'true', 'tab-index': '-1' }`
	 */
	properties?: Record<string, string>;
}
```

## CustomEvents

For side effects, you can subscribe to `toc.activeItem` and `toc.items`, which are powered Svelte `$state` runes. Alternatively, you may listen to `tocinit` or `tocchange` [CustomEvent] on the `toc` root element:

```svelte title=custom-toc-events.svelte
<script lang="ts">
  import { toc } from '@svelte-put/toc';
  import type { TocInitEventDetail, TocChangeEventDetail } from '@svelte-put/toc';

  const toc = new Toc({ observe: true });
  function handleTocInit(event: CustomEvent<TocInitEventDetail>) {
    const { items } = event.detail;
    console.log('Extracted item', items);
  }
  function handleTocChange(event: CustomEvent<TocChangeEventDetail>) {
    const { activeItem } = event.detail;
    console.log('Item currently on viewport', activeItem);
  }
</script>

<!-- :::highlight -->
<main use:toc.actions.root ontocinit={handleTocInit} ontocchange={handleTocChange}>
<!-- ::: -->
  ...
</main>
```

<div class="c-callout c-callout--info">

**Runtime Expectation**

`tocinit` is only fired once. And whether `tocchange` is fired depends on the `observe` option (See [Observing In View Element][toc.observe] for more information). Specifically:

- When `observe` is `false`, expect no `tocchange` [CustomEvent]. This makes sense because all necessary information has been extracted at initialization.
- When `observe` is `true`, expect a `tocchange` [CustomEvent] that follows shortly after `tocinit`. The `observe` property of each extracted `TocItem` is only guaranteed to be populated in this `tocchange` event and not `tocinit`. This is because `observe` initialization operations are run asynchronously to avoid blocking any potential work with the extracted information from `tocinit` (such as rendering the table of content itself).

</div>

<h2 id="toc-data-attributes">Toc Data Attributes</h2>

This section lists all `data-*` attributes used by `toc`. See the full [type definition here](https://github.com/vnphanquang/svelte-put/blob/adb89085b72a09b43285ca392ca1b7552641d203/packages/toc/src/attributes/attributes.d.ts).

### On Toc Elements

Options provided to the `toc` action parameter, such as `threshold` or `strategy`, are global and affect all matching toc elements. Attributes listed below can be used to override behavior of `toc` per matching element. All of them are `undefined` by default.

```typescript title=TocElementDataAttributes
interface TocElementDataAttributes {
	/** whether to ignore this element when searching for matching elements */
	'data-toc-ignore'?: boolean;
	/**
	 * the `id` to use for this element in `toc` context. If not provided, this
	 * will be the element `id`, or generated by `toc`
	 * if element does not have an `id` either.
	 */
	'data-toc-id'?: string;
	/**
	 * override the `strategy` for this element to use in creating
	 * `IntersectionObserver` This only has effect if the `observe`
	 * option is enabled in {@link TocParameters}
	 */
	'data-toc-strategy'?: TocObserveConfig['strategy'];
	/**
	 * override the `threshold` for this element to use in creating
	 * `IntersectionObserver` This only has effect if the `observe`
	 * option is enabled in {@link TocParameters}
	 */
	'data-toc-threshold'?: number;
}
```

### By Observe Operation

The following attributes are utilized by the `observe` operation when enabled. Notice some of them are `readonly`, which means they are handled internally by `observe` and should not be changed manually.

```typescript title=TocObserveDataAttributes
interface TocObserveDataAttributes {
	/**
	 * added to the element where IntersectionObserver is used when observe is
	 * turned on and references the associated toc element
	 */
	readonly 'data-toc-observe-for'?: string;
	/**
	 * added to toc root (the element where toc action is placed on) and
	 * references the id of the active matching element
	 *
	 * This attribute is reactive. When changed (either by toc or manually),
	 * it will trigger events and update to Toc properties accordingly
	 */
	'data-toc-observe-active-id'?: string;
	/**
	 * added to toc root (the element where toc action is placed on) and
	 * indicate whether observe is being throttled, typically seen in conjunction
	 * with usage of the complementary toclink action
	 */
	readonly 'data-toc-observe-throttled'?: boolean;
	/**
	 * added to the element where toclink is used and
	 * set to true when the linked toc element is active
	 */
	readonly 'data-toc-link-active'?: boolean;
}
```

### Reference Markers

The following attributes act as **readonly** reference markers added by `toc` (or [@svelte-put/preprocess-auto-slug]).

```typescript title=TocReferenceMarkerDataAttributes
interface TocReferenceMarkerDataAttributes {
/**
	 * marking this element that it's been processed by toc
	 *
	 * If this is already preprocessed by {@link https://svelte-put.vnphanquang.com/docs/preprocess-auto-slug | @svelte-put/preprocess-auto-slug},
	 * there will also be a `data-auto-slug` attribute.
	 */
	readonly 'data-toc'?: '';
	/**
	 * if the anchor option is enabled in toc parameters, this attribute is present on the injected anchor element.
	 *
	 * If the element is already added by {@link https://svelte-put.vnphanquang.com/docs/preprocess-auto-slug | @svelte-put/preprocess-auto-slug},
	 * there `data-auto-slug-anchor` attribute is found instead.
	 */
	readonly 'data-toc-anchor'?: '';
	/**
	 * added to the element where toc action is used for internal reference
	 */
	readonly 'data-toc-root'?: '';
	/**
	 * added to the element where toclink action is used and references the linked toc element
	 */
	readonly 'data-toc-link-for'?: '';

	/**
	 * from {@link https://svelte-put.vnphanquang.com/docs/preprocess-auto-slug | @svelte-put/preprocess-auto-slug}
	 */
	'data-auto-slug'?: '';
	'data-auto-slug-anchor'?: '';
	'data-auto-slug-anchor-position'?: '';
}
```

<h2 id="migration-guides">Migration Guides</h2>

### V5 -> V6 (Svelte 5 in Runes mode)

The [Svelte-store-based][Svelte store] `TocStore` interface has been dropped in favor for the new [Toc Class][toc.class], which is now powered by Svelte runes, providing a much more minimal and powerful API. Whereas `TocStore` was optional, creating a `Toc` instance is now a required step:

- start by replacing `createTocStore` with `new Toc()`,
- move the parameters passed to previously `toc` action to now `Toc` `new` call,
- use the actions `toc.actions.root` and `toc.actions.link` instead of `toc` and `toc-link`, and
- change references to `$tocStore` to `toc`

```svelte title="tocStore is now just toc"
<script>
  // :::diff -
  import { toc, createTocStore, toclink } from '@svelte-put/toc';
  // :::
  // :::diff +
  import { Toc } from '@svelte-put/toc';
  // :::

  // :::diff -
  const tocStore = createTocStore();
  // :::
  // :::diff +
  const toc = new Toc({ observe: true });
  // :::
</script>

<!-- :::highlight -->
<main use:toc={{ store: tocStore, observe: true }}>
<!-- ::: -->
<!-- :::diff + -->
<main use:toc.actions.root>
<!-- ::: -->
  <h1>Page Heading</h1>

  <section>
    <h2>Table of Contents</h2>
		<!-- :::diff - -->
    {#if $tocStore.items.size}
		<!-- ::: -->
		<!-- :::diff + -->
    {#if toc.items.size}
		<!-- ::: -->
      <ul>
				<!-- :::diff - -->
        {#each $tocStore.items.values() as tocItem}
				<!-- ::: -->
				<!-- :::diff + -->
        {#each toc.items.values() as tocItem}
				<!-- ::: -->
          <li>
            <!-- svelte-ignore a11y-missing-attribute -->
						<!-- :::diff - -->
            <a use:toclink={{ store: tocStore, tocItem, observe: true }}></a>
						<!-- ::: -->
						<!-- :::diff + -->
            <a use:toc.actions.link={tocItem}></a>
						<!-- ::: -->
          </li>
        {/each}
      </ul>
    {/if}
  </section>
</main>
```

Additionally, you should change the event directive syntax to just regular attributes (remove `:`):

```svelte title="on:event is now onevent"
<!-- :::diff - -->
<main use:toc={{ observe: true }} on:tocinit on:tocchange>
<!-- ::: -->
<!-- :::diff - -->
<main use:toc.actions.root ontocinit on:tocchange>
<!-- ::: -->
```

### V4 -> V5

From version 5, the `items` property of `TocStore` and `TocInitEventDetail` is now a [Map] instead of plain object as in version 4. This enables better performance and properly preserves the order of collected toc elements.

```svelte title="Migration v4 -> v5"
<section>
  <h2>Table of Contents</h2>
  <!-- :::diff - -->
  {#if Object.values($tocStore.items).length}
  <!-- ::: -->
  <!-- :::diff + -->
  {#if $tocStore.items.size}
  <!-- ::: -->
    <ul>
      <!-- :::diff - -->
      {#each Object.values($tocStore.items) as tocItem}
      <!-- ::: -->
      <!-- :::diff + -->
      {#each $tocStore.items.values() as tocItem}
      <!-- ::: -->
        <li>
          ...
        </li>
      {/each}
    </ul>
  {/if}
</section>
```

---

Happy making table of contents! 👨‍💻

[Quick Start]: #quick-start
[toc.class]: #toc-class
[toc.anchor]: #toc-anchor
[toc.actions.root]: #toc-actions-root
[toc.actions.link]: #toc-actions-link
[toc.observe]: #observing-in-view-element

[@svelte-put/preprocess-auto-slug]: /docs/preprocess-auto-slug
[issue]: https://github.com/vnphanquang/svelte-put/issues
[CustomEvent]: https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent
[Map]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map
[Svelte action]: https://svelte.dev/docs/svelte-action
[Svelte store]: https://svelte.dev/docs/svelte-store
[janosh/svelte-toc]: https://github.com/janosh/svelte-toc
[IntersectionObserver]: https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API

